﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reflection;
using nRoute.Components.Collections;
using nRoute.Components.Messaging;
using nRoute.Utilities;
#if (WINRT)
using Windows.ApplicationModel.Resources.Core;
using System.Threading.Tasks;
using System.Reflection;
#endif
#if (!COMPOSITION)
using System.Windows;
using System.Xml;
#endif

namespace nRoute.Mapping
{
    public static class AssemblyMapper
	{
        private static readonly ConcurrentOrderedList<Assembly> _mappedAssemblies;
        private static readonly Object _lock = new Object();

        static AssemblyMapper() 
        {
            _mappedAssemblies = new ConcurrentOrderedList<Assembly>();
        }

#region Attributes Related

        public static void GetAttributesInMappedAssembly<T>(Assembly assembly, Action<T, Type> mapAction)
            where 
                T : Attribute
        {
            Guard.ArgumentNotNull(assembly, "assembly");
            Guard.ArgumentNotNull(mapAction, "mapAction");

            // we get all the attributes type , note_ a type can have more than one instance 
            // of the given attribute applied..
#if (WINRT)
            var _types = assembly.DefinedTypes;
#else
            var _types = assembly.GetTypes();
#endif

            var _mappedAttributes = (from _type in _types
#if (WINRT)
                                     let _attribs = _type.GetCustomAttributes<T>(false).ToList()
#else
                                     let _attribs = _type.GetCustomAttributes(typeof(T), false).Cast<T>().ToList()
#endif
                                    where (_attribs != null && _attribs.Count > 0)
                                    select new MappedAttributes<T>()
                                    { 
#if (WINRT)
                                        TargetType = _type.AsType(),
#else
                                        TargetType = _type, 
#endif
                                        TargetAttributes = _attribs 
                                    }).ToList();

            // we specifically add all the assembly-level attributes
#if (WINRT)
            var _assemblyAttributes = assembly.GetCustomAttributes<T>().ToArray();
#else
            var _assemblyAttributes = assembly.GetCustomAttributes(typeof(T), false);
#endif
            if (_assemblyAttributes != null && _assemblyAttributes.Length > 0)
            {
                _mappedAttributes.Add(new MappedAttributes<T>()
                {
                    TargetType = assembly.GetType(),
                    TargetAttributes = _assemblyAttributes.Cast<T>().ToList()
                });
            }

            // we get all the applications of the a given attribute
          	foreach (var _mappedAttribute in _mappedAttributes)
			{
				// we need to check for each possible attribute applied 
				foreach (var _attribute in _mappedAttribute.TargetAttributes)
				{
				    mapAction(_attribute, _mappedAttribute.TargetType);	
				}				
			}			
		}

        public static void GetAttributesInMappedAssemblies<T>(Action<T, Type> mapAction)
            where 
                T : Attribute
		{
            Guard.ArgumentNotNull(mapAction, "mapAction");

            var _assemblies = MappedAssemblies;
            foreach (var _assembly in _assemblies)
            {
                GetAttributesInMappedAssembly<T>(_assembly, mapAction);
            }
		}

#endregion

#region Assembly Related

#if (WINRT)
        public static async Task GetApplicationAssemblies()
#else
        public static void GetApplicationAssemblies()
#endif
        {
#if (WINRT)
            // we get all the included assemblies
            var _applicationAssembly = await GetManifestIncludedAssemblies();
            //var _applicationAssembly = new List<Assembly>();
            //_applicationAssembly.Add(typeof(AssemblyMapper).GetTypeInfo().Assembly);
            
            lock (_lock)
            {
#else
            lock (_lock)
            {
                var _applicationAssembly = GetManifestIncludedAssemblies();
#endif

                // and map all listed assemblies - note_ the mapping is actually using an observable channel
                foreach (var _assembly in _applicationAssembly)
                {
                    if (_mappedAssemblies.TryAdd(_assembly))
                    {
                        Channel.Publish<MappedAssemblyInfo>(new MappedAssemblyInfo(_assembly));
                    }
                }
            }
		}

        public static void MapAssembly(Assembly assembly)
		{
            Guard.ArgumentNotNull(assembly, "assembly");

            lock (_lock)
            {
                if (!_mappedAssemblies.Contains(assembly))
                {
                    if (_mappedAssemblies.TryAdd(assembly))
                    {
                        Channel.Publish<MappedAssemblyInfo>(new MappedAssemblyInfo(assembly));
                    }
                }
            }
		}

        public static bool IsAssemblyMapped(Assembly assembly)
		{
            lock (_lock)
            {
                // note_ in SL, we can't unload an assembly once loaded
                return _mappedAssemblies.Contains(assembly);
            }
		}

        public static IEnumerable<Assembly> MappedAssemblies 
		{
			get
			{
                Assembly[] _assemblies = null;
                lock (_lock)
                {
                    _assemblies = _mappedAssemblies.ToArray();
                }
                return _assemblies;
			}
		}

#endregion

#region Helpers

#if ( !WINRT)

        public static IEnumerable<Assembly> GetManifestIncludedAssemblies()
        {

#if !SILVERLIGHT

            return AppDomain.CurrentDomain.GetAssemblies();

#else

#if DEBUG 
            if (DesignerProperties.IsInDesignTool)
            {
                // tip from http://joshsmithonwpf.wordpress.com/2010/01/06/the-ultimate-hack-for-silverlight-in-blend/
                return typeof (AppDomain).GetMethod("GetAssemblies").Invoke(AppDomain.CurrentDomain, null) as Assembly[];
            }
#endif

            // ideas from http://pagebrooks.com/archive/2009/05/17/a-smaller-xap-preloader-for-silverlight.aspx
            // load from app manifest the zip stream
            var _applicaAssemblies = new List<Assembly>();
            var _manifestStream = Application.GetResourceStream(new Uri("AppManifest.xaml", UriKind.Relative));

            // get the xml reader
            string _manifest = new StreamReader(_manifestStream.Stream).ReadToEnd();
            XmlReader _reader = XmlReader.Create(new StringReader(_manifest));

            // read all the AssemblyPart elements
            while (_reader.Read())
            {
                if (_reader.IsStartElement("AssemblyPart"))
                {
                    // we get the source attribute which has the dll file name
#if (WP7)
                    _reader.MoveToAttribute("Name", "http://schemas.microsoft.com/winfx/2006/xaml");
#else
                    _reader.MoveToAttribute("Source");
#endif
                    _reader.ReadAttributeValue();

                    // get the name of the assembly, and check if it has been 
                    var _assemblyName = _reader.Value;
                    var _isAlreadyLoaded = _applicaAssemblies.FirstOrDefault((a) =>
                        string.Equals(a.ManifestModule.ToString(), _assemblyName, 
                            StringComparison.InvariantCultureIgnoreCase)) != null;

                    // if not already loaded
                    if (!_isAlreadyLoaded)
                    {
#if (WP7)
                        var _assembly = Assembly.Load(_assemblyName);
   
#elif (SILVERLIGHT)
                        // we get the assembly
                        var _assembly = default(Assembly);
                        var _stream = Application.GetResourceStream(
                            new Uri(_assemblyName, UriKind.Relative));

                        // create a assembly part to load
                        AssemblyPart _assemblyPart = new AssemblyPart();
                        _assembly = _assemblyPart.Load(_stream.Stream);
#else
                        // we get the assembly
                        var _assembly = Assembly.Load(_assemblyName);
#endif

                        // and add to the list
                        _applicaAssemblies.Add(_assembly);
                    }
                }
            }

            // and return
            return _applicaAssemblies;

#endif

        }

#elif (WINRT)

        // see http://social.msdn.microsoft.com/Forums/en-US/winappswithcsharp/thread/6e8d015b-4c3b-4e1a-9884-d986ba5935c0/
        // and https://gist.github.com/1234767
        // Note: this should be a temporary workaround till the feature/concept is fully/properly baked into WinRT
        public static async Task<IEnumerable<Assembly>> GetManifestIncludedAssemblies()
        {
            var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;

            var _resourceManager = ResourceManager.Current;
            var _resources = _resourceManager.AllResourceMaps;

            List<Assembly> assemblies = new List<Assembly>();
            foreach (Windows.Storage.StorageFile file in await folder.GetFilesAsync())
            {
                if (file.FileType == ".dll" || file.FileType == ".exe")
                {
                    var _fileName = file.Name.Substring(0, file.Name.Length - file.FileType.Length);
                    AssemblyName _name = new AssemblyName() { Name = _fileName };
                    Assembly _asm = Assembly.Load(_name);
                    assemblies.Add(_asm);
                }
            }
            return assemblies;
        }

#else
        // updated to SL4 with WP 7.1 SDK to use Load Method (see above)
        //public static IEnumerable<Assembly> GetManifestIncludedAssemblies()
        //{
        //    // This is a technique from SL2 - and it should work coz all the assemblies should be inter-used in someways
        //    // if not then one might have to call load assembly individually
        //    var _loadedAssemblies = new List<Assembly>();
        //    var _assemblyLister = null as Action<Assembly, List<Assembly>>;

        //    // recurusive lister
        //    _assemblyLister = (a, l) =>
        //    {
        //        if (_loadedAssemblies.Contains(a)) return;
        //        _loadedAssemblies.Add(a);
        //        foreach (var m in a.GetModules())
        //        {
        //            if (!_loadedAssemblies.Contains(m.Assembly))
        //                _assemblyLister(m.Assembly, _loadedAssemblies);
        //        }
        //    };

        //    // and we load starting with the three assembly references we known off
        //    _assemblyLister(Assembly.GetCallingAssembly(), _loadedAssemblies);
        //    _assemblyLister(Assembly.GetExecutingAssembly(), _loadedAssemblies);
        //    _assemblyLister(System.Windows.Application.Current.GetType().Assembly, _loadedAssemblies);

        //    return _loadedAssemblies;
        //}

#endif

#endregion

#region Private Class

        private class MappedAttributes<T>
            where 
                T : Attribute
        {
            public Type TargetType { get; set; }

            public IEnumerable<T> TargetAttributes { get; set; }
        }

#endregion

    }
}